package org.zyj.io.protocal.length_field_based_frame;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import org.zyj.io.protocal.Message;

import java.nio.ByteOrder;

/**
 * 根据协议的二进制格式, 来分割frame
 * 输入内容是 length + header + body
 * +----------+----------+----------------+          +----------+----------------+
 * |  Length  | Header 1 | Actual Content |  ----->  | Header 1 | Actual Content |
 * | 0x00000C |  0xCAFE  | "HELLO, WORLD" |          |  0xCAFE  | "HELLO, WORLD" |
 * +----------+----------+----------------+          +----------+----------------+
 * 这里
 * - length是4字节
 * - header12字节
 * -------
 * 我们想要构成frame 是 header + body; 那么我们设定如下的值
 * - lengthFieldOffset = 0; 表示length从那个位置开始
 * - lengthFieldLength = 4; 表示length字段的长度
 * - lengthAdjustment = 12; 由于length字段仅仅表示的是body的长度, 而我们又需要在frame中加上header的长度
 * - initialBytesToStrip = 4; 我们的header+body, 是从第5个字节开始, 所以前面4个字节抛弃
 */
public class LengthFieldBasedFrameMessageDecoder extends LengthFieldBasedFrameDecoder {

    private static final int maxFrameLength = Integer.MAX_VALUE; //发送的数据帧最大长度

    //↓↓↓↓核心控制参数
    private static final int lengthFieldOffset = 0; //定义长度域位于发送的字节数组中的下标

    //此例中是仅表示body的长度
    private static final int lengthFieldLength = 4; //用于描述定义的长度域的长度, 这个字段可以 表示整个报文和body, 但会对lengthAdjustment的使用有影响

    //由于lengthFieldLength是表示body的长度, 这里是 version,opCode,streamId 的长度
    private static final int lengthAdjustment = 12; //长度域的偏移量矫正。 如果长度域的值，除了包含有效数据域的长度外，还包含了其他域（如长度域自身）长度，那么，就需要进行矫正。矫正的值为：包长 - 长度域的值 – 长度域偏移 – 长度域长。
    private static final int initialBytesToStrip = 4; //丢弃的起始字节数。丢弃处于有效数据前面的字节数量。比如前面有4个节点的长度域，则它的值为4。
    //↑↑↑↑核心控制参数

    private static final boolean failFast = true; //读取到长度域超过maxFrameLength，就抛出一个 TooLongFrameException。false: 只有真正读取完长度域的值表示的字节之后，才会抛出 TooLongFrameException，默认情况下设置为true，建议不要修改，否则可能会造成内存溢出
    private static final ByteOrder byteOrder = ByteOrder.BIG_ENDIAN; //数据存储采用大端模式或小端模式

    public LengthFieldBasedFrameMessageDecoder() {
        //构造函数中的几个值, 决定了decode函数输入的是什么样的值
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame = (ByteBuf) super.decode(ctx, in); //这里已经帮你把报文处理成帧frame
        //这里传入的 ByteBuf in是处理过半包粘包问题的数据
        int version = frame.readInt();
        int opCode = frame.readInt();
        int streamId = frame.readInt();
        int bodyLength = frame.readableBytes();
        byte[] body = new byte[bodyLength];
        for (int i = 0; i < bodyLength; i++) {
            body[i] = frame.readByte();
        }
        //组装message对象
        Message message = new Message();
        message.setVersion(version);
        message.setOpCode(opCode);
        message.setStreamId(streamId);
        message.setBody(body);
        return message;
    }

}
